package org.syncsoft.overalt.communication.api;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.security.auth.login.LoginException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.syncsoft.overalt.common.api.Configuration;
import org.syncsoft.overalt.common.api.Parameter;
import org.syncsoft.overalt.common.exception.EntityServerError;
import org.syncsoft.overalt.common.json.EntityCodeJsonConverter;
import org.syncsoft.overalt.communication.exception.ConnectionException;
import org.syncsoft.overalt.communication.impl.IntegerCollectionConnectionEntityConverter;
import org.syncsoft.overalt.communication.impl.StringConnectionEntityConverter;
import org.syncsoft.overalt.domain.community.entity.Community;
import org.syncsoft.overalt.domain.community.json.CommunitiesJsonConverter;
import org.syncsoft.overalt.domain.document.entity.Document;
import org.syncsoft.overalt.domain.document.entity.Permission;
import org.syncsoft.overalt.domain.document.joson.DocumentJsonConverter;
import org.syncsoft.overalt.domain.document.joson.DocumentsJsonConverter;
import org.syncsoft.overalt.domain.document.joson.PermissionJsonConverter;
import org.syncsoft.overalt.domain.tag.entity.Tag;
import org.syncsoft.overalt.domain.user.entity.User;
import org.syncsoft.overalt.domain.user.json.UsersJsonConverter;

/**
 * implements ICommunication for a.m.i.g.o.s. using HTTP/HTTPS.
 * 
 * @author tptfc
 *
 */
public class Communication implements ICommunication {
	private static final String ID = "id";
	private static ICommunication instance;
	
	static {
		instance = new Communication(); 
	}
	
	private Communication() {
		
	}
	
	public static ICommunication getInstance() {
		return instance;
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getDocumentFile(int)
	 */
	@Override
	public Collection<Integer> getDocumentFile(int fileId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(fileId)));
		
		Collection<Integer> fileInteger = get(ServerUrl.getFile(), parameters, new IntegerCollectionConnectionEntityConverter());
		
		fileInteger = isValidFile(fileInteger);
		
		return fileInteger;
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#delete(int)
	 */
	@Override
	public void delete(int fileId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(fileId)));
		
		post(ServerUrl.deleteFile(), parameters, new StringConnectionEntityConverter());
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#addFile(org.syncsoft.overalt.domain.document.entity.Document, java.io.File)
	 */
	@Override
	public Integer addFile(Document document, File file) {
		List<Parameter> parameters = getDefaltDocumentParameters(document);
		String json = postMultipart(ServerUrl.createFile(), parameters, Configuration.getDocumentFileEntityName(), file, new StringConnectionEntityConverter());
		return new EntityCodeJsonConverter().converter(json);	

	}
	
	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#commit(java.lang.String, org.syncsoft.overalt.domain.document.entity.Document, java.io.File)
	 */
	@Override
	public Integer commit(String versionDescription, Document document, File file) {
		List<Parameter> parameters = getDefaltDocumentParameters(document);
		parameters.add(new Parameter(Configuration.getDocumentCodeEntityName(), String.valueOf(document.getCode())));
		parameters.add(new Parameter(Configuration.getDocumentVersionedEntityName(), "true"));
		parameters.add(new Parameter(Configuration.getDocumentCommentEntityName(), versionDescription));
		
		String json = postMultipart(ServerUrl.commitFile(), parameters, Configuration.getDocumentFileEntityName(), file, new StringConnectionEntityConverter());
		return new EntityCodeJsonConverter().converter(json);
		
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getDocument(int)
	 */
	@Override
	public Document getDocument(int fileID) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(fileID)));
		String json = post(ServerUrl.getDocument(), parameters, new StringConnectionEntityConverter());
		
		Document document = new DocumentJsonConverter().converter(json);
		if (document != null && document.getPolicy() != null) {
			Permission permission = getPermission(document.getPolicy().getCode());
			document.getPolicy().setPermission(permission);
		}
		
		return document;
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getCommunityMembers(int)
	 */
	@Override
	public Collection<User> getCommunityMembers(int communityId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(communityId)));
		String json = post(ServerUrl.getCommunityMebers(), parameters, new StringConnectionEntityConverter());
		
		return new UsersJsonConverter().converter(json);
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getUserCommunities(int)
	 */
	@Override
	public Collection<Community> getUserCommunities(int userId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(userId)));
		String json = post(ServerUrl.getUserCommunities(), parameters, new StringConnectionEntityConverter());
		
		return new CommunitiesJsonConverter().converter(json);
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getUserContacts(int)
	 */
	@Override
	public Collection<User> getUserContacts(int userId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(userId)));
		String json = post(ServerUrl.getUserContacts(), parameters, new StringConnectionEntityConverter());

		return new UsersJsonConverter().converter(json);
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getCommunityFiles(int)
	 */
	@Override
	public Collection<Document> getCommunityFiles(int commId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(commId)));
		String json = post(ServerUrl.getCommunityFiles(), parameters, new StringConnectionEntityConverter());
		
		return new DocumentsJsonConverter().converter(json);
	}

	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getUserFiles(int)
	 */
	@Override
	public Collection<Document> getUserFiles(int userId) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(userId)));
		String json = post(ServerUrl.getUserFiles(), parameters, new StringConnectionEntityConverter());
		
		return new DocumentsJsonConverter().converter(json);
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getUserCode(java.lang.String, java.lang.String)
	 */
	@Override
	public Integer getUserInSessionCode() {
		String result = get(ServerUrl.getHome(), null, new StringConnectionEntityConverter());
		int beginIndex = result.indexOf("var USER_IN_SESSION_CODE = '");
		
		result = result.substring(beginIndex);
		result = result.substring(result.indexOf("'") + 1, result.indexOf("';"));
		
		Integer code;
		
		try {
			code = Integer.parseInt(result.trim());
		} catch (NumberFormatException e) {
			code = null;
		}
		
		return code;
	}
	
	/*
	 * (non-Javadoc)
	 * @see org.syncsoft.overalt.communication.api.ICommunication#getPermission(int)
	 */
	@Override
	public Permission getPermission(int code) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(ID, String.valueOf(code)));
		String json = post(ServerUrl.getUserFiles(), parameters, new StringConnectionEntityConverter());
		
		return new PermissionJsonConverter().converter(json);
	}
	

	/**
	 * 
	 * @param document
	 * @return
	 */
	private List<Parameter> getDefaltDocumentParameters(Document document) {
		List<Parameter> parameters = new ArrayList<Parameter>();
		parameters.add(new Parameter(Configuration.getDocumentTitleEntityName(), document.getTitle()));
		parameters.add(new Parameter(Configuration.getDocumentDescriptionEntityName(), document.getDescription()));
		
		String tags = "";
		
		if (document.getTags() != null) {
			for (Tag tag : document.getTags()) {
				tags += tag.getDescription() + ",";
			}
		}
		
		if (document.getCommunities() != null) {
			for (Community community : document.getCommunities()) {
				parameters.add(new Parameter(Configuration.getDocumentCommunitiesEntityName(), String.valueOf(community.getCode())));
			}
		}
		
		parameters.add(new Parameter(Configuration.getDocumentTagsEntityName(), tags));
		
		String permission = "1";
		
		if (document.getPolicy() != null && Permission.PRIVATE.equals(document.getPolicy().getPermission())) {
			permission = "3";
		}
		
		parameters.add(new Parameter(Configuration.getDocumentPolicyEntityName(), permission));
		
		return parameters;
	}
	
	/**
	 * @precondition uri != null
	 * @postcondition execute a HTTP request to uri using the GET method
	 * @param uri
	 * @param parameters
	 * @return
	 * @throws ConnectionException 
	 * @throws LoginException 
	 */
	protected <T> T get(String uri, List<Parameter> parameters, IConnectionEntityConverter<T> converter) {
		Connection connection = ConnectionPool.getConnection();
		T converted = null;
		
		HttpEntity entity = getEntity(connection.get(uri, parameters));
		converted = convertHttpEntity(entity, converter);
		
		ConnectionPool.closeConnection();
		
		return converted;
	}
	
	/**
	 * @precondition uri != null
	 * @postcondition execute a HTTP request to uri using the POST method
	 * @param uri
	 * @param parameters
	 * @return
	 * @throws ConnectionException 
	 * @throws LoginException 
	 */
	protected <T> T post(String uri, List<Parameter> parameters, IConnectionEntityConverter<T> converter) {
		Connection connection = ConnectionPool.getConnection();
		HttpEntity entity;
		T converted = null;
		
		entity = getEntity(connection.post(uri, parameters));
		converted = convertHttpEntity(entity, converter);
		
		ConnectionPool.closeConnection();
		
		return converted;
	}
	
	/**
	 * @precondition uri != null
	 * @postcondition execute a HTTP request to uri using the POST method
	 * @param uri
	 * @param parameters
	 * @return
	 * @throws ConnectionException 
	 * @throws LoginException 
	 */
	protected <T> T postMultipart(String uri, List<Parameter> parameters, String fileEntityName, File file, IConnectionEntityConverter<T> converter) {
		Connection connection = ConnectionPool.getConnection();
		HttpEntity entity;
		T converted = null;
		
		entity = getEntity(connection.postMultipart(uri, parameters, fileEntityName, file));
		converted = convertHttpEntity(entity, converter);
		
		ConnectionPool.closeConnection();
		
		return converted;
	}
	
	/**
	 * @precondition response != null
	 * @param response
	 * @return
	 */
	protected HttpEntity getEntity(HttpResponse response) {
		HttpEntity entity = null;
		
		if (response != null) {
			entity = response.getEntity();
			
			if (response.getStatusLine().getStatusCode() > 399) {
				String error = convertHttpEntity(entity, new StringConnectionEntityConverter());
				throw new EntityServerError(error);
			}
		}
		
		return entity;
	}

	/**
	 * @postcondition entity != null
	 * @postcondition don't altered the entity instance
	 * @param entity
	 * @return
	 * @throws ConnectionException 
	 */
	private <T> T convertHttpEntity(HttpEntity entity, IConnectionEntityConverter<T> converter) {
		converter.process(entity);
		return converter.result();
	}

	/**
	 * @precondition fileInteger != null
	 * @postcondition return null if isn't a file
	 *  
	 * @param fileInteger
	 * @return
	 */
	private Collection<Integer> isValidFile(Collection<Integer> fileInteger) {
		if (fileInteger != null && fileInteger.size() < 50) {
			StringBuilder builder = new StringBuilder();
			for (int i : fileInteger) {
				builder.append((char)i);
			}
			
			if (builder.toString().startsWith("resc.exception.serverError")) {
				fileInteger = null;
			}
		}
		return fileInteger;
	}
}
